/* Team 5
 * James McQueen
 * Corwyn Simpson
 * May 1 ish, 2012
 */
package spider.ui;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.Map;

import javax.swing.*;

import spider.controllers.Parser;
import spider.controllers.ParserGroup;
import spider.controllers.Retriever;
import spider.controllers.RetrieverGroup;
import spider.models.Data;
import spider.stores.DataGatherer;
import spider.stores.LinkBuffer;
import spider.stores.PageBuffer;

/**
 * Definition of a Reporter, used in tracking the keywords appearing in a
 * web crawlers visited links.
 * @author James McQueen
 */
public class Reporter 
{
    /**
     * Starting string definition of properly formatted link.
     */
    public static final String HTTP_PREFIX = "http://";
    /**
     * Default URL to search with if URL is not specified.
     */
    public static final String DEFAULT_URL = "www.comcast.net";
    /**
     * Extra filter placed on the Retriever in case seed is a restricted domain.
     */
    public static final String BLOG_AVOID = "questioneverything.typepad.com";
    /**
     * Constant value for appending a new line.
     */
    public static final String NEXT_LINE = "\n";
    /**
     * Constant value for appending a tab.
     */
    public static final String TAB = "\t";
    /**
     * Constant value for record bookshelf.
     */
    public static final String END_START = "  ------------------------------  ";
    
    /**
     * Indicates the start of a record.
     */
    public static final String START_RECORD = NEXT_LINE + END_START + 
            "Result data record START" + END_START + NEXT_LINE;
    /**
     * Indicates the end of a record.
     */
    public static final String END_RECORD = END_START + 
            "Result data record END" + END_START + NEXT_LINE + 
            NEXT_LINE;
    
    /**
     * Default width of the frame.
     */
    public static final int WIDTH = 928;
    /**
     * Default height of the frame.
     */
    public static final int HEIGHT = 650;
    /**
     * Color used for frame design.
     */
    public static final Color CUSTOM_COLOR = new Color(55,50,200);
    /**
     * Color used for text.
     */
    public static final Color TEXT_COLOR = Color.WHITE.brighter();
     /**
     * Color used for text.
     */
    public static final Color TEXT2_COLOR = Color.YELLOW;
    /**
     * Font used for buttons, title and progress bar.
     */
    public static final Font DEFAULT_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 16);
    /**
     * Font used for input fields.
     */
    public static final Font INPUT_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 14);
    /**
     * Font used for the output JTextArea.
     */
    public static final Font OUTPUT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, 12);
    /**
     * Defines the action for single threaded crawl process.
     */
    private final AbstractAction my_single_crawl = new CrawlAction();
     /**
     * Defines the action for multi threaded crawl process.
     */
    private final AbstractAction my_multi_crawl = new CrawlMultiAction();
    
    /**
     * Used to track the opposing buffer capacities, determining which works harder.
     */
    private JProgressBar my_work_bar = new JProgressBar(0, 100);
    
    /**
     * Used to track the completion of the crawl.
     */
    private JProgressBar my_overall_progress_bar = new JProgressBar();
    /**
     * Text field for defining the seed URL (starting URL).
     */
    private JTextField my_seed_field;
    /**
     * Text field for defining the list of keywords to search for.
     */
    private JTextField my_keywords_field;
     /**
     * Text field for defining the max number of pages to visit.
     */
    private JTextField my_max_field;
    /**
     * Text field for defining the number of buffer threads.
     */
    private JTextField my_buffer_thread_field;
    /**
     * Text field for defining the number of parser threads.
     */
    private JTextField my_parser_thread_field;
    /**
     * Label for the method of crawl being used.
     */
    private JLabel my_approach_label;
    
    private JTextArea my_result_area;
    private JFrame my_frame;
    private JPanel my_title_panel;
    private JPanel my_output_panel;
    private TimerPanel my_timer_panel;
    
    private LinkBuffer my_links;
    private PageBuffer my_pages;
    private DataGatherer my_gatherer;
    private ParserGroup my_parser_group;
    private Parser my_parser;
    private Retriever my_retriever;
    private RetrieverGroup my_retriever_group;
   
    private String[] my_keywords = {"a", "new", "hope"};
    
    private URL my_seed_url;
    private int my_max_search;
    private int my_buffer_threads;
    private int my_parser_threads;
    
    private double my_total_time;
    private double my_single_time;
    private long my_start_time;
    private long my_end_time;

    /**
     * Description of a Reporter.  The reporter is responsible for setup of the underlying
     * data structures (top-level container) and displays the results of the crawl.
     */
    public Reporter()
    {
        my_links = new LinkBuffer();
        my_pages = new PageBuffer(100);
        my_gatherer = new DataGatherer();
        setupGui();
    }
    
    /**
     * Show the frame, allowing the user to begin 'Spidering'.
     */
    public void start() 
    {
        my_frame.pack();
        my_frame.setResizable(true);
        my_frame.setVisible(true);
    }
    
    /**
     * Setup the GUI. Sets up the GUI, calling the other initialization
     * helper methods.
     */
    private void setupGui() 
    {
        initFrame();
        initButtons();
        initTitle();
        initInputs();
        initReporting();
    }
    
    /**
     * Initialize the frame for the GUI.
     */
    private void initFrame() 
    {
        my_frame = new JFrame("TCSS 422 - Web Spider (Corwyn Simpson, James McQueen)");
        my_frame.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        my_frame.setLayout(new BorderLayout());
        my_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        my_frame.setForeground(TEXT2_COLOR);
    }
   
    /**
     * Setup the button panel used to select the type of Spider to use.
     */
    private void initButtons() 
    {
        JPanel button_panel = new JPanel(new FlowLayout());
        JButton crawl_button = new JButton(my_single_crawl);
        crawl_button.setFont(DEFAULT_FONT);
       
        JButton crawl_multi_button = new JButton(my_multi_crawl);
        crawl_multi_button.setFont(DEFAULT_FONT);
        
        JButton save_button = new JButton(new SaveLogAction());
        save_button.setFont(DEFAULT_FONT);
        
        button_panel.add(crawl_button);
        button_panel.add(crawl_multi_button);
        button_panel.add(save_button);
        button_panel.setBackground(CUSTOM_COLOR.darker());
        
        my_frame.add(button_panel, BorderLayout.SOUTH);
    }

    /**
     * Setup title panel used to display the type of Spider in use.
     */
    private void initTitle() 
    {
        my_title_panel = new JPanel();
        my_approach_label = new JLabel("Select (Single or Multi) Threaded Approach");
        my_approach_label.setForeground(TEXT2_COLOR);
        my_approach_label.setFont(DEFAULT_FONT);
        my_title_panel.add(my_approach_label);
        my_title_panel.setBackground(CUSTOM_COLOR.darker());

        my_frame.add(my_title_panel, BorderLayout.NORTH);
    }

    /**
     * Setup the input panel with the default URL, number of sites to check etc.
     */
    private void initInputs() 
    {
        my_seed_field = new JTextField("faculty.washington.edu/gmobus/");
        my_seed_field.setFont(INPUT_FONT);
        my_seed_field.setHorizontalAlignment(JTextField.CENTER);
        JLabel seed_url_label = new JLabel("Seed URL: ");
        seed_url_label.setFont(DEFAULT_FONT);
        seed_url_label.setLabelFor(my_seed_field);
        seed_url_label.setForeground(TEXT2_COLOR);
        seed_url_label.setHorizontalAlignment(JLabel.CENTER);

        my_keywords_field = new JTextField("intelligence,artificial," +
        		"agent,university,research,science,robot");
        my_keywords_field.setFont(INPUT_FONT);
        my_keywords_field.setHorizontalAlignment(JTextField.CENTER);
        JLabel keywords_label = new JLabel("Keywords");
        keywords_label.setFont(DEFAULT_FONT);
        keywords_label.setLabelFor(my_seed_field);
        keywords_label.setForeground(TEXT2_COLOR);
        keywords_label.setHorizontalAlignment(JLabel.CENTER);

        my_max_field = new JTextField("100", 5);
        my_max_field.setFont(INPUT_FONT);
        my_max_field.setHorizontalAlignment(JTextField.CENTER);
        JLabel max_search_label = new JLabel("Search Limit: ");
        max_search_label.setFont(DEFAULT_FONT);
        max_search_label.setLabelFor(my_max_field);
        max_search_label.setForeground(TEXT2_COLOR);
        max_search_label.setHorizontalAlignment(JLabel.CENTER);
        
        my_parser_thread_field = new JTextField("10", 2);
        my_parser_thread_field.setFont(INPUT_FONT);
        my_parser_thread_field.setHorizontalAlignment(JTextField.CENTER);
        JLabel parser_thread_label = new JLabel("Parser Threads: ");
        parser_thread_label.setFont(DEFAULT_FONT);
        parser_thread_label.setLabelFor(my_parser_thread_field);
        parser_thread_label.setForeground(TEXT2_COLOR);
        parser_thread_label.setHorizontalAlignment(JLabel.CENTER);
        
        my_buffer_thread_field = new JTextField("10", 2);
        my_buffer_thread_field.setFont(INPUT_FONT);
        my_buffer_thread_field.setHorizontalAlignment(JTextField.CENTER);
        JLabel buffer_thread_label = new JLabel("Retriever Threads: ");
        buffer_thread_label.setFont(DEFAULT_FONT);
        buffer_thread_label.setLabelFor(my_buffer_thread_field);
        buffer_thread_label.setForeground(TEXT2_COLOR);
        buffer_thread_label.setHorizontalAlignment(JLabel.CENTER);
        
        JPanel holder_panel = new JPanel(new GridLayout(2,1,2,1));
        JPanel input_panel = new JPanel(new GridLayout(14,2,2,2));
        input_panel.setBackground(CUSTOM_COLOR);
        input_panel.add(seed_url_label);
        input_panel.add(my_seed_field);
        input_panel.add(keywords_label);
        input_panel.add(my_keywords_field);
        input_panel.add(max_search_label);
        input_panel.add(my_max_field);
        
        input_panel.add(parser_thread_label);
        input_panel.add(my_parser_thread_field);
        input_panel.add(buffer_thread_label); 
        input_panel.add(my_buffer_thread_field);
        
        JPanel blank_panel = new JPanel();
        JPanel blank_panel2 = new JPanel();
        blank_panel.setBackground(CUSTOM_COLOR);
        blank_panel2.setBackground(CUSTOM_COLOR);
        input_panel.add(blank_panel);
        input_panel.add(my_overall_progress_bar);
        input_panel.add(new JLabel());
        my_work_bar.setBorderPainted(true);
        my_work_bar.setString("Retreivers vs Parsers");
        my_work_bar.setStringPainted(true);
        
        my_overall_progress_bar.setBorderPainted(true);
        my_overall_progress_bar.setString("Search Progress");
        my_overall_progress_bar.setStringPainted(true);
        
        input_panel.add(my_work_bar);
        input_panel.setPreferredSize(new Dimension(280, HEIGHT / 2));
        input_panel.setMinimumSize(new Dimension(260, HEIGHT / 2));
        input_panel.setMaximumSize(new Dimension(300, HEIGHT / 2));
        my_timer_panel = new TimerPanel();
        
        holder_panel.add(input_panel);
        holder_panel.add(my_timer_panel);
        
        my_frame.add(holder_panel, BorderLayout.WEST);
    }
    
   /**
    * Setup the output area for reporting the current statistics.
    */
   private void initReporting()
   {
       my_result_area = new JTextArea(END_START + "  Web Crawler Result Set  " + END_START);
       my_result_area.setFont(OUTPUT_FONT);
       my_result_area.setEditable(false);
       my_result_area.append(NEXT_LINE);
       JScrollPane scroll_pane = new JScrollPane(my_result_area, 
               JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        my_output_panel = new JPanel();
        my_output_panel.setLayout(new GridLayout(1,1));
        my_output_panel.add(scroll_pane);
        my_frame.add(my_output_panel);     
        my_frame.pack();
   }

   private void initTime()
   {
       my_single_time = 0;
       my_total_time = 0;
       my_start_time = 0;
       my_end_time = 0;
   }
   /**
    * Responsible for displaying the crawl statistics to the output area.
    */
   private void report()
   {
       my_result_area.append(START_RECORD + TAB);
       my_result_area.append("links.isEmpty = " + my_links.isEmpty());
       my_result_area.append(TAB);
       my_result_area.append("links.size = " + my_links.size());
       my_result_area.append(TAB);
       my_result_area.append("links.visited = " + my_links.visitedCount() + 
                       "/" + my_max_search);
       
       my_result_area.append(NEXT_LINE + TAB);
       my_result_area.append("pages.isEmpty = " + my_pages.isEmpty());
       my_result_area.append(TAB);
       my_result_area.append("pages.remainingCapacity = " + my_pages.remainingCapacity());
       if (my_gatherer.getSourceCount() > 0)
       {
           my_result_area.append(NEXT_LINE + TAB);
           my_result_area.append("result count: \n" + formatData());
       }
       
       my_result_area.append(NEXT_LINE + TAB);
       my_result_area.append("data.sourceCount: " + my_gatherer.getSourceCount());
       
       my_result_area.append(NEXT_LINE + TAB);
       my_result_area.append("single cycle time: " + (my_single_time/1000.) + " seconds.");
       
       my_result_area.append(NEXT_LINE + TAB);
       my_result_area.append("total elapsed time: " + (my_total_time/1000.) + " seconds.");
       
       my_result_area.append(NEXT_LINE + NEXT_LINE);
       my_result_area.append(END_RECORD); 
       my_result_area.setCaretPosition(my_result_area.getDocument().getLength());
       
       my_timer_panel.setTimes(my_total_time, my_single_time);
       //my_progress_bar.setValue(my_pages.remainingCapacity());
       //my_timer_panel.repaint();
   }
   
   private String formatData()
   {
       StringBuilder result = new StringBuilder();
       int total = 0;
       result.append("         keyword    |" + //20 chars + colon
       		"  avg/page |" +
       		"  total   \n");
       for (Map.Entry<String, Integer> keyword : 
           my_gatherer.getCumulativeResults().entrySet())
       {
           if ("*".equals(keyword.getKey()))
           {
               total = keyword.getValue();
           }
           else
           {
               result.append(String.format("%20s: %10.3f, %10d\n", 
                             keyword.getKey(), 
                             keyword.getValue() / (double) my_gatherer.getSourceCount(), 
                             keyword.getValue()));
           }
       }
       result.append(String.format("    total word count: %10.3f, %10d\n", 
                     total / (double)my_gatherer.getSourceCount(), 
                     total));
       return result.toString();
   }
   
   private void finalReport()
   {
       my_result_area.append("Crawling concluded\n");
   }
   
   private void timeStart()
   {
       if ((my_single_time) > 0)
       {
           my_total_time += my_single_time;
           //System.out.println("my_total_time(" + my_total_time + ") += my_single_time(" + my_single_time + ");");
       }
       my_start_time = System.currentTimeMillis();
   }
   
   private void timeStop()
   {
       my_end_time = System.currentTimeMillis();
       my_single_time =  my_end_time - my_start_time;
       my_total_time += my_single_time;
   }
    
   /**
     * Sets the default URL and checks validity of the input.  Input is normalized for link
     * creation allowing users to type 'comcast.net' instead of 'http://comcast.net/' into the input
     * text field. Moreover users are prevented from inputting the restricted blog.  A default value
     * is set if any of the above conditions are violated.
     */
    private void setDefaultURL() 
    {
        //TODO: Update the text box associated with this field
        String temp_text = my_seed_field.getText().trim().toLowerCase();
        try 
        {
            if(!temp_text.startsWith(HTTP_PREFIX));
            {
                temp_text = (HTTP_PREFIX + temp_text);
            }
            if (!(temp_text.endsWith(".html") || temp_text.endsWith(".htm") || 
                            temp_text.endsWith(".txt") || temp_text.endsWith("/")))
            {
                temp_text = temp_text + "/";
            }
            
            if(temp_text.contains(BLOG_AVOID))
            {
                
                my_seed_url = new URL(DEFAULT_URL);
            }
            else
            {
                my_seed_url = new URL(temp_text);
                System.out.println("my_seed_url: " + my_seed_url.toString());
            }
        } 
        catch (MalformedURLException ex) 
        {
            System.err.println("Error with setDefaultURL: " + ex.getCause());
        }
    }
    
    /**
     * Sets the set of keywords to search for when running the spider.
     */
    private void setKeywords()
    {
        my_keywords = my_keywords_field.getText().split(",");
    }

    /**
     * Sets the maximum search size and checks validity of the input.  
     * A default max search value is set if any of the above conditions are violated.
     */
    private void setMaxSearch() 
    {
        //Setting the maximum value
        String temp_max = my_max_field.getText().trim();
        try 
        {
            my_max_search = (int)Integer.valueOf(temp_max);
            my_overall_progress_bar.setMinimum(0);
            my_overall_progress_bar.setMaximum(my_max_search);
            if(my_max_search < 1)
            {
                //Invalid situation set our own default value.
                my_max_search = 50;
                my_max_field.setText(my_max_search + "");
            }
        } 
        catch (NumberFormatException e) 
        {
            System.err.println("Error with set_max_search: " + e.getCause());
        }
       // System.out.println("my_max_search: " + my_max_search);
    }
    
    public void barTimer()
    {
        new Thread(new Runnable() 
            {
                public void run() 
                {
                    try 
                    {
                        String original = my_overall_progress_bar.getString();
                        while (my_parser_group.isRunning())
                        {
                            Thread.sleep(250);
                            my_work_bar.setValue(my_pages.remainingCapacity());
                            my_work_bar.repaint();
                            my_overall_progress_bar.setValue(my_gatherer.getSourceCount());
                            my_overall_progress_bar.setString(my_gatherer.getSourceCount() + "/" + my_max_search);
                            my_overall_progress_bar.repaint();
                        }
                        my_overall_progress_bar.setString(original);
                    } 
                    catch (InterruptedException e) 
                    {
                        System.err.println("Bar Updater interrupted");
                    }
                }
            }, "BarTimer").start();
    }
    /**
     * Sets the number of threads used and checks validity of the input.  
     * A default thread size is set if any of the above conditions are violated.
     */
    private void setThreadSizes() 
    {
        //Setting the thread sizes
        String parser_thread = my_parser_thread_field.getText().trim();
        String buffer_thread = my_buffer_thread_field.getText().trim();
        try 
        {
            my_parser_threads = (int)Integer.valueOf(parser_thread);
            my_buffer_threads = (int)Integer.valueOf(buffer_thread);
            
            if(my_parser_threads < 1)
            {
                //Invalid situation set our own default thread sizes.
                my_parser_threads = 10;
            }

            if(my_buffer_threads < 1)
            {
                //Invalid situation set our own default thread sizes.
                my_buffer_threads = 10;
            }  
        } 
        catch (NumberFormatException e) 
        {
            System.err.println("Error with setThreadSize(): " + e.getCause());
        }
    }
    
    /**
     * Start Crawling the Web Single-threaded approach.
     *
     * @author James McQueen
     */
    @SuppressWarnings("serial")
    private class CrawlAction extends AbstractAction 
    {

        /**
         * Constructs a CrawlAction.
         */
        public CrawlAction() 
        {
            super("Single Crawl");
            putValue(Action.SHORT_DESCRIPTION, "Single Threaded");
            putValue(Action.MNEMONIC_KEY, KeyEvent.VK_S);
            putValue(Action.SELECTED_KEY, false);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void actionPerformed(final ActionEvent the_event) 
        {
            my_approach_label.setText("Single Thread Approach");
            my_approach_label.repaint();
            setDefaultURL();
            setKeywords();
            setMaxSearch();
            my_gatherer.clearResults();
            my_links.clear();
            my_pages.clear();
            my_result_area.setText("");
            my_retriever_group = new RetrieverGroup(my_links, my_pages, my_max_search, 1);
            my_parser_group = new ParserGroup(my_gatherer, my_links, my_pages, my_keywords, 1);
           
            if (my_seed_url == null) 
            {
                try 
                {
                    my_links.addLink(new URL("http://www.comcast.net"));
                }
                catch (MalformedURLException ex) 
                {
                    System.err.println("ActionEvent exception:\n" + ex.getCause());
                }
            } 
            else 
            {
                my_links.addLink(my_seed_url); 
            }

            initTime();
            final boolean[] is_running = {true};
            new Thread(new Runnable()
            {
                public void run()
                {
                    while (!my_links.isEmpty() && my_links.visitedCount() < my_max_search) 
                    {
                        try 
                        {
                            timeStart();
                            barTimer();
                            my_start_time = System.currentTimeMillis();
                            while (my_pages.isEmpty())
                            {
                                //Retrieve a page and do retriever stuff.
                                my_retriever = new Retriever(my_retriever_group);
                                my_retriever.run();
                                my_retriever_group.storePage(my_retriever);
                            }
        
                            //Parse a page and do parser stuff
                            my_parser = new Parser(my_parser_group);
                            my_parser.run();
                            my_parser_group.gatherResults(my_parser);
                            
                            timeStop();
                            my_output_panel.repaint();
                        } 
                        catch (InterruptedException exl) 
                        {
                            System.err.println("ActionEvent while loop exception:\n" + exl.getCause());
                        }
                    }
                    is_running[0] = false;
                }
            },"SingleThread").start();
            new Thread(new Runnable() 
            {
                public void run() 
                {
                    try 
                    {
                        barTimer();
                        while (is_running[0]) 
                        {                     
                            Thread.sleep(1000); //one second
                            report();                            
                        }
                        finalReport();
                    } 
                    catch (InterruptedException e) 
                    {
                        //do nothing
                    }
                }
            }, "PeriodicReporter").start();

        }
    }

    /**
     * Start Crawling the Web Multi-threaded approach.
     *
     * @author James McQueen
     */
    @SuppressWarnings("serial")
    private class CrawlMultiAction extends AbstractAction 
    {

        /**
         * Constructs a CrawlFasterAction.
         */
        public CrawlMultiAction() 
        {
            super("Multi Crawl");
            putValue(Action.SHORT_DESCRIPTION, "Multi Threaded");
            putValue(Action.MNEMONIC_KEY, KeyEvent.VK_M);
            putValue(Action.SELECTED_KEY, false);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void actionPerformed(final ActionEvent the_event) 
        {
            my_approach_label.setText("Multi Threaded Approach");
            //Initialize the max search value from the max search field text field input.
            setMaxSearch();
            setKeywords();
            setThreadSizes();
            my_gatherer.clearResults();
            my_pages.clear();
            my_links.clear();
            my_result_area.setText("");
            my_retriever_group = new RetrieverGroup(my_links, my_pages, 
                            my_max_search, my_buffer_threads);
            my_parser_group = new ParserGroup(my_gatherer, 
                            my_links, my_pages, my_keywords, my_parser_threads);

            my_parser_group.watchRetrieverGroup(my_retriever_group);
            my_retriever_group.watchParserGroup(my_parser_group);
           
            //Initialize the default URL from the text field input.
            setDefaultURL();
            
            
            if (my_seed_url == null)
            {
                try 
                {
                    my_links.addLink(new URL("http://www.comcast.net"));
                } 
                catch (MalformedURLException ex) 
                {
                    System.err.println("ActionEvent exception:\n" + ex.getCause());
                }
            }
            else
            {
                my_links.addLink(my_seed_url);
            }
            
            my_start_time = System.currentTimeMillis();
            new Thread(my_retriever_group, "RetrieverGroup").start();
            new Thread(my_parser_group, "ParserGroup").start();
            //Start any other threads for UI and such here
            new Thread(new Runnable() 
            {
                public void run() 
                {
                    try 
                    {
                        barTimer();
                        while (my_parser_group.isRunning()) 
                        {                     
                            Thread.sleep(1000); //one second
                            report();                            
                            my_total_time = System.currentTimeMillis() - my_start_time;
                            my_single_time = my_total_time / my_gatherer.getSourceCount();
                        }
                        finalReport();
                    } 
                    catch (InterruptedException e) 
                    {
                        //do nothing
                    }
                }
            }, "PeriodicReporter").start();
        }
    }
    
    /**
     * Log the text in my_text_area to a file given by the user.
     * @author Corwyn Simpson
     */
    private class SaveLogAction extends AbstractAction
    {
        /**
         * 
         */
        private static final long serialVersionUID = 6954722116831680575L;
        private JFileChooser my_chooser;
        public SaveLogAction()
        {
            super("Save Log");
            my_chooser = new JFileChooser();
            putValue(Action.SHORT_DESCRIPTION, "Save the log output to a file");
            putValue(Action.MNEMONIC_KEY, KeyEvent.VK_V);
            putValue(Action.SELECTED_KEY, false);
        }

        @Override
        public void actionPerformed(ActionEvent the_event)
        {
            int log_type = JOptionPane.showOptionDialog(my_frame,
                            "What should be logged to file?", "Logging choice", 
                            JOptionPane.DEFAULT_OPTION, 
                            JOptionPane.PLAIN_MESSAGE, null, 
                            new String[] {"Times only", "Displayed", "Complete"}, null);
 
            if (log_type != JOptionPane.CLOSED_OPTION &&
                my_chooser.showSaveDialog(my_frame) == JFileChooser.APPROVE_OPTION)
            {
                final String filename = my_chooser.getSelectedFile().getAbsolutePath();
              
                try
                {
                    final java.io.Writer out = new java.io.FileWriter(filename, true);
                    
                    out.write("\n");
                    out.write(new Date(System.currentTimeMillis()).toString());
                    out.write("\nRun with "+ my_parser_threads +" parser threads");
                    out.write("and "+ my_buffer_threads +" retriever threads\n");
                    switch (log_type)
                    {
                        case 2: //Debugging
                            out.write("=====Individual page hits=====\n");
                            for (Data data : my_gatherer.getIndividualResults())
                            {
                                out.write(data.toString() +"\n");
                            }
                            out.write("\n\n\n");
                        //intentional fall-thru
                        case 1: //Complete
                            out.write(my_result_area.getText());
                        //intentional fall-thru
                        case 0: //Brief
                            out.write("last cycle time: "+ my_single_time +"ms\n");
                            out.write("total elapsed time: "+ my_total_time + "ms\n");
                        break;
                    }
                    out.flush();
                    out.close();
                }
                catch (final IOException e)
                {
                    System.err.println("Unable to write to file: " + filename);
                }
            }
        }
    }
}
